package gui.threed;

import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import labyrinth.Labyrinth;
import labyrinth.Position;
import agent.IAgent;
import agent.moves.IAgentMove;

import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.universe.SimpleUniverse;

/**
 * This class represents a Universe, which shows a labyrinth
 * 
 * @author Johannes Kulick
 * @version 0.1
 * @licence GNU Lesser General Public License
 */
public class LabyrinthUniverse extends SimpleUniverse {
	/**
	 * The shown labyrinth.
	 */
	private Labyrinth mLabyrinth;
	private BranchGroup mAgentGroup;
	private IAgent mAgent;
	private LabyrinthModel mLabyrinthGroup;
	/**
	 * The duration of a one-field movement (in ms)
	 */
	private double mDuration = 100;

	/**
	 * C'tor which sets up the viewing platform
	 * 
	 * @param labArea
	 *            A Canvas 3D which will paint the Universe
	 */
	public LabyrinthUniverse(Canvas3D labArea) {
		super(labArea);

		// Move the viewing platform (the viewpoint...) to a better place
		TransformGroup vpGroup = getViewingPlatform()
				.getViewPlatformTransform();

		// Move it
		Transform3D vpTranslate = new Transform3D();
		vpTranslate.set(new Vector3f(0.0f, 22.0f, 25f));

		// Rotate it
		Transform3D vpRotate = new Transform3D();
		vpRotate.rotX(-0.8);

		// combine
		vpTranslate.mul(vpRotate);

		// apply
		vpGroup.setTransform(vpTranslate);

		// Add rotate, zoom etc. to mouse events with a OrbitBehavior
		OrbitBehavior orbit = new OrbitBehavior(labArea,
				OrbitBehavior.REVERSE_ALL | OrbitBehavior.STOP_ZOOM);
		BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
				100.0);
		orbit.setSchedulingBounds(bounds);

		getViewingPlatform().setViewPlatformBehavior(orbit);
	}

	/**
	 * Shows a given Labyrinth in the Universe
	 * 
	 * @param labyrinth
	 */
	public void showLabyrinth(Labyrinth labyrinth) {
		if (mLabyrinthGroup != null) {
			getLocale().removeBranchGraph(mLabyrinthGroup);
		}
		mLabyrinth = labyrinth;
		mLabyrinthGroup = labyrinth.get3dView();
		mLabyrinthGroup.setCapability(BranchGroup.ALLOW_DETACH);
		addBranchGraph(mLabyrinthGroup);
	}

	/**
	 * Adds an agent model to the virtual universe and removes the old one
	 * 
	 * @param agent
	 *            The agent to add
	 */
	public void setAgent(IAgent agent) {
		// remove old agent...
		if (mAgentGroup != null) {
			getLocale().removeBranchGraph(mAgentGroup);
		}

		mAgentGroup = new BranchGroup();
		mAgent = agent;

		float xOffset = (mLabyrinth.getMatrix().length - 1)
				* LabyrinthModel.BLOCK_SIZE;
		float yOffset = (mLabyrinth.getMatrix()[0].length - 1)
				* LabyrinthModel.BLOCK_SIZE;

		// get the startpoint
		Position startpoint = mLabyrinth.getStartPoint();
		int x = startpoint.x;
		int y = startpoint.y;

		// move to the startpoint
		TransformGroup agentStart = new TransformGroup();
		Transform3D translate = new Transform3D();
		translate.set(new Vector3d(2 * x * LabyrinthModel.BLOCK_SIZE - xOffset,
				agent.getModelHeight() / 2, 2 * y * LabyrinthModel.BLOCK_SIZE
						- yOffset));
		agentStart.setTransform(translate);
		agentStart.addChild(agent.getModel());
		agentStart.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

		mAgentGroup.addChild(agentStart);
		mAgentGroup.setCapability(BranchGroup.ALLOW_DETACH);

		addBranchGraph(mAgentGroup);
	}

	/**
	 * Moves the agent the specified move
	 * 
	 * @param nextMove
	 *            The next move to do
	 * @throws InterruptedException
	 */
	public void moveAgent(IAgentMove nextMove) throws InterruptedException {
		// Get Trasformation group of the agent
		TransformGroup agent = (TransformGroup) mAgentGroup.getChild(0);

		// Get old position
		Transform3D old = new Transform3D();
		agent.getTransform(old);
		Transform3D transformed = new Transform3D();
		Vector3d croped = new Vector3d();

		// interpolate to new position for animation
		for (double i = 0; i <= mDuration; i++) {
			nextMove.getTransformation().get(croped);
			croped.scale(i / mDuration);

			transformed.set(croped);
			transformed.mul(old);
			agent.setTransform(transformed);
			// sleep...
			Thread.sleep(1);
		}
	}

	/**
	 * Resets the Agent to the startpoint
	 */
	public void reset() {

		float xOffset = (mLabyrinth.getMatrix().length - 1)
				* LabyrinthModel.BLOCK_SIZE;
		float yOffset = (mLabyrinth.getMatrix()[0].length - 1)
				* LabyrinthModel.BLOCK_SIZE;

		// get the startpoint
		Position startpoint = mLabyrinth.getStartPoint();
		int x = startpoint.x;
		int y = startpoint.y;

		// Get Trasformation group of the agent
		TransformGroup agent = (TransformGroup) mAgentGroup.getChild(0);

		// Get old position
		Transform3D translate = new Transform3D();
		agent.getTransform(translate);

		// move to the startpoint
		translate.set(new Vector3d(2 * x * LabyrinthModel.BLOCK_SIZE - xOffset,
				mAgent.getModelHeight() / 2, 2 * y * LabyrinthModel.BLOCK_SIZE
						- yOffset));
		agent.setTransform(translate);
	}
}
